﻿/* Copyright (c) 2010 Lexalytics Inc.

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

// Needed for Office and Excel integration
using Excel = Microsoft.Office.Interop.Excel;

// Needed for business classes
using LexaScopeNET;
using Lexalytics;
using Lexalytics4Excel.Dialogs;

namespace Lexalytics4Excel.SheetHelpers
{
    /// <summary>
    /// Class for performing representation of Entity results in Excel worksheet
    /// </summary>
    public class EntityHelper
    {
        private DataProject _entityProject;
        private int _entityType;
        private string _entityTabName = "";
        private string _entityTypeString = "";
        private string _entityTypeSingularString = "";
        private Excel.Worksheet _wsEntity;
        private SortedList<int, LXAEntity> _entityResults = new SortedList<int, LXAEntity>();
        private SortedList<string, int> _topEntity = new SortedList<string, int>();
        private int _nCurrentEntityNum = 1;
        private int _nCurrentSheetRow = 1;
        private int _kMinEntityDetailsRows = 5;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="EntityDataProject">DataProject containing data and results for display</param>
        /// <param name="EntityType">Type of entity to create a worksheet of results for</param>
        public EntityHelper(DataProject EntityDataProject, int EntityType)
        {
            _entityProject = EntityDataProject;
            _entityType = EntityType;

            switch (_entityType)
            {
                case (int)LXAEntityType.Types.Person:
                    _entityTabName = "People";
                    _entityTypeString = "People";
                    _entityTypeSingularString = "Person";
                    break;
                case (int)LXAEntityType.Types.Company:
                    _entityTabName = "Companies";
                    _entityTypeString = "Companies";
                    _entityTypeSingularString = "Company";
                    break;
                case (int)LXAEntityType.Types.Place:
                    _entityTabName = "Places";
                    _entityTypeString = "Places";
                    _entityTypeSingularString = "Place";
                    break;
                case (int)LXAEntityType.Types.Product:
                    _entityTabName = "Products";
                    _entityTypeString = "Products";
                    _entityTypeSingularString = "Product";
                    break;
            }
        }

        /// <summary>
        /// Public method to generate results header
        /// </summary>
        public int GenerateResultsSheet()
        {
            int nRetCode = 0;
            nRetCode = CreateEntityWorksheet();
            if (nRetCode == 0)
            {
                GenerateTopEntitiesHeader("","");
            }
            return nRetCode;
        }

        /// <summary>
        /// Public method to generate results header
        /// </summary>
        /// <param name="FilterColumn">Column used for filtering results</param>
        public int GenerateResultsSheet(string FilterColumn)
        {
            int nRetCode = 0;
            nRetCode = CreateEntityWorksheet();
            if (nRetCode == 0)
            {
                GenerateTopEntitiesHeader(FilterColumn, "");
            }
            return nRetCode;
        }

        /// <summary>
        /// Public method to generate results header
        /// </summary>
        /// <param name="FilterColumn">Column used for filtering results</param>
        /// <param name="FilterValue">Current value for filtering</param>
        public int GenerateResultsSheet(string FilterColumn, string FilterValue)
        {
            int nRetCode = 0;
            nRetCode = CreateEntityWorksheet();
            if (nRetCode == 0)
            {
                GenerateTopEntitiesHeader(FilterColumn, FilterValue);
            }
            return nRetCode;
        }

        /// <summary>
        /// Adds current results into results sheet
        /// </summary>
        public void GenerateResultsSection()
        {
            InsertEntityResults("");
        }

        /// <summary>
        /// Adds current results into results sheet
        /// </summary>
        /// <param name="FilterValue">Current value for filtering</param>
        public void GenerateResultsSection(string FilterValue)
        {
            InsertEntityResults(FilterValue);
        }

        /// <summary>
        /// Creates the worksheet for entity results, deleting any existing if confirmed by user.
        /// </summary>
        /// <returns>Integer status code</returns>
        private int CreateEntityWorksheet()
        {
            // Retain name of current worksheet so we can return to it
            Excel.Application _excel = Globals.LexalyticsAddIn.Application;
            Excel.Workbook _book = _excel.ActiveWorkbook;

            foreach (Excel.Worksheet ws in _book.Sheets)
            {
                if (ws.Name == _entityTabName)
                {
                    ResultsSheetCreate dlgCreateResults = new ResultsSheetCreate(_entityTabName,_book);
                    if (dlgCreateResults.ShowDialog() == DialogResult.Cancel)
                        return -1;
                    else
                    {
                        if (!dlgCreateResults.OverwriteTab)
                            _entityTabName = dlgCreateResults.TabName;
                        else
                            ws.Delete();
                        break;
                    }
                }
            }

            this._wsEntity = (Excel.Worksheet)Globals.LexalyticsAddIn.Application.Sheets.Add(Type.Missing, Type.Missing, Type.Missing, Excel.XlSheetType.xlWorksheet);
            _wsEntity.Name = _entityTabName;
            _wsEntity.Visible = Excel.XlSheetVisibility.xlSheetVisible;

            return 0;
        }

        /// <summary>
        /// Generates sheet output of the top entities retrieved from results
        /// </summary>
        /// <param name="FilterColumn">String containing name of sort or filter column, if applicable</param>
        /// <param name="FilterValue">String containing filter value, if applicable</param>
        private void GenerateTopEntitiesHeader(string FilterColumn, string FilterValue)
        {
            if (_wsEntity == null)
                return;

            // Set header
            Excel.Range r = _wsEntity.get_Range(_wsEntity.Cells[1, "A"], _wsEntity.Cells[1, "I"]);
            r.Merge(true);
            if (!String.IsNullOrEmpty(FilterColumn) && !String.IsNullOrEmpty(FilterValue))
                r.Value2 = String.Format("Top {0} ({1} equals {2})", _entityTypeString, FilterColumn, FilterValue);
            else if (!String.IsNullOrEmpty(FilterColumn) && (String.IsNullOrEmpty(FilterValue)))
                r.Value2 = String.Format("Top {0} (sorted by {1})", _entityTypeString, FilterColumn);
            else
                r.Value2 = String.Format("Top {0}", _entityTypeString);
            r.Font.Bold = true;
            r.Font.Size = 14;
            r.Font.Name = "Calibri";
            r.WrapText = false;
            r.Interior.Color = ColorTranslator.ToOle(System.Drawing.Color.FromArgb(216, 216, 216));

            // Set subheader
            r = (Excel.Range)_wsEntity.Cells[2, "A"];      
            r.Value2 = String.Format("{0} name (click for details)",_entityTypeSingularString);
            r.ColumnWidth = 42;
            r.Font.Italic = true;
            r.Font.Size = 11;
            r.Font.Name = "Calibri";
            r.WrapText = false;

            r = (Excel.Range)_wsEntity.Cells[2, "B"];
            r.Value2 = "# of documents";
            r.ColumnWidth = 13;
            r.Font.Italic = true;
            r.Font.Size = 11;
            r.Font.Name = "Calibri";
            r.WrapText = false;

            r = (Excel.Range)_wsEntity.Cells[2, "C"];
            r.Value2 = "# of mentions";
            r.ColumnWidth = 13;
            r.Font.Italic = true;
            r.Font.Size = 11;
            r.Font.Name = "Calibri";
            r.WrapText = false;

            r = (Excel.Range)_wsEntity.Cells[2, "D"];
            r.Value2 = "P/N ratio";
            r.ColumnWidth = 13;
            r.Font.Italic = true;
            r.Font.Size = 11;
            r.Font.Name = "Calibri";
            r.WrapText = false;

            if (!String.IsNullOrEmpty(FilterColumn) && (String.IsNullOrEmpty(FilterValue)))
            {
                r = (Excel.Range)_wsEntity.Cells[2, "E"];
                r.Value2 = FilterColumn;
                r.ColumnWidth = 13;
                r.Font.Italic = true;
                r.Font.Size = 11;
                r.Font.Name = "Calibri";
                r.WrapText = false;
            }

            _nCurrentSheetRow = 3;
            _nCurrentEntityNum = 1;
        }

        /// <summary>
        /// Inserts a block of entity results, separated from creation of header to allow results to be added on.
        /// </summary>
        /// <param name="FilterValue">Current value for filtered results</param>
        private void InsertEntityResults(string FilterValue)
        {
            // Ensure we have right results for this section
            switch (_entityType)
            {
                case (int)LXAEntityType.Types.Person:
                    _entityResults = _entityProject.PeopleResults;
                    break;
                case (int)LXAEntityType.Types.Company:
                    _entityResults = _entityProject.CompanyResults;
                    break;
                case (int)LXAEntityType.Types.Place:
                    _entityResults = _entityProject.PlaceResults;
                    break;
                case (int)LXAEntityType.Types.Product:
                    _entityResults = _entityProject.ProductResults;
                    break;
            }

            foreach (int nEntity in _entityResults.Keys)
            {
                LXAEntity myEntity = (LXAEntity)_entityResults[nEntity];
                
                // TODO: Replace with read from data project
                myEntity.NeutralLowerBound = -0.5f;
                myEntity.NeutralUpperBound = 0.5f;

                Excel.Range r = (Excel.Range)_wsEntity.Cells[_nCurrentSheetRow, "A"];
                r.Value2 = myEntity.Entity;
                string rangeName = String.Format("LXAEntityDetails_{0}",_nCurrentEntityNum);
                r.Hyperlinks.Add(r, "", String.Format("{0}!{1}", _wsEntity.Name, rangeName), Type.Missing, Type.Missing);

                r = (Excel.Range)_wsEntity.Cells[_nCurrentSheetRow, "B"];
                r.Value2 = myEntity.MentionList.Count.ToString();

                r = (Excel.Range)_wsEntity.Cells[_nCurrentSheetRow, "C"];
                r.Value2 = myEntity.Mentions.ToString();

                r = (Excel.Range)_wsEntity.Cells[_nCurrentSheetRow, "D"];
                r.Value2 = myEntity.PNRatio.ToString();

                // Set row style
                r = _wsEntity.get_Range(_wsEntity.Cells[_nCurrentSheetRow, "B"], _wsEntity.Cells[_nCurrentSheetRow, "E"]);
                r.Font.Size = 10;
                r.Font.Name = "Calibri";
                r.WrapText = false;

                // Output filter value if relevant
                if (!String.IsNullOrEmpty(FilterValue))
                {
                    r = (Excel.Range)_wsEntity.Cells[_nCurrentSheetRow, "E"];
                    r.Value2 = FilterValue;
                }

                // Insert the entity mentions
                _nCurrentSheetRow++;
                _nCurrentEntityNum++;
                InsertEntityMentions(myEntity, rangeName, ref _nCurrentSheetRow);
            }
        }

        /// <summary>
        /// Inserts the individual mentions for a given entity
        /// </summary>
        /// <param name="aEntity">The entity object containing the mentions list</param>
        /// <param name="rangeName">String to identify Excel NamedRange to create for details</param>
        /// <param name="nCurrentRow">Row of sheet to start on</param>
        private void InsertEntityMentions(LXAEntity aEntity, string rangeName, ref int nCurrentRow)
        {
            int nBeginRow = nCurrentRow;

            // Set header
            Excel.Range r = (Excel.Range)_wsEntity.Cells[nCurrentRow, "B"];
            r.Value2 = "Document";
            r = (Excel.Range)_wsEntity.Cells[nCurrentRow, "C"];
            r.Value2 = "# Mentions";
            r = (Excel.Range)_wsEntity.Cells[nCurrentRow, "D"];
            r.Value2 = "Entity Sentiment";

            // Set header style
            r = _wsEntity.get_Range(_wsEntity.Cells[nCurrentRow, "B"], _wsEntity.Cells[nCurrentRow, "E"]);
            r.ColumnWidth = 14;
            r.Font.Italic = true;
            r.Font.Size = 11;
            r.Font.Name = "Calibri";
            r.WrapText = false;

            foreach (LXAEntityMention aMention in aEntity.MentionList)
            {
                string targetRow = null;
                string targetLink = null;
                bool bRowColor = false;
                if (Lexalytics4Excel.Properties.Settings.Default["RowColoring"] != null)
                    bRowColor = Convert.ToBoolean(Lexalytics4Excel.Properties.Settings.Default["RowColoring"]);

                if (aMention.EntityMetadata.ContainsKey("rowindex"))
                    targetRow = aMention.EntityMetadata["rowindex"];
                if (!String.IsNullOrEmpty(targetRow))
                {
                    nCurrentRow++;
                    r = (Excel.Range)_wsEntity.Cells[nCurrentRow, "B"];
                    r.Value2 = String.Format("Row {0}", targetRow);
                    targetLink = String.Format("'{0}'!{1}{2}", _entityProject.UserRangeWorksheet, _entityProject.UserRangeTextColumn, targetRow);
                    r.Hyperlinks.Add(r, "", targetLink, Type.Missing, Type.Missing);

                    r = (Excel.Range)_wsEntity.Cells[nCurrentRow, "C"];
                    r.Value2 = String.Format("{0}", aMention.Mentions);

                    r = (Excel.Range)_wsEntity.Cells[nCurrentRow, "D"];
                    // CJL 2009-08-27
                    // Magnitude value includes pos/neg sentiment sign
                    r.Value2 = String.Format("{0}", aMention.Magnitude);

                    // Set entity mention style
                    r = _wsEntity.get_Range(_wsEntity.Cells[nCurrentRow, "B"], _wsEntity.Cells[nCurrentRow, "D"]);
                    r.Font.Size = 10;
                    r.Font.Name = "Calibri";
                    r.WrapText = false;
                    if (bRowColor)
                    {
                        if (aMention.Sentiment > 0)
                            r.Cells.Interior.ColorIndex = 43;
                        else if (aMention.Sentiment < 0)
                            r.Cells.Interior.ColorIndex = 3;
                        else
                            r.Cells.Interior.ColorIndex = 33;
                    }
                }
            }

            // Ensure that entity details have minimum of 5 rows
            if (nCurrentRow - nBeginRow < _kMinEntityDetailsRows)
                nCurrentRow = nBeginRow + _kMinEntityDetailsRows;

            // Insert sentiment pie chart
            InsertEntitySentimentChart(aEntity, nBeginRow, nCurrentRow);

            // Name the range
            r = (Excel.Range)_wsEntity.get_Range(String.Format("A{0}", nBeginRow), String.Format("D{0}", nCurrentRow));
            Excel.Name nameEntityDetails = _wsEntity.Names.Add(rangeName, r, true,
                                            Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                                            Type.Missing, Type.Missing, Type.Missing, Type.Missing);
            r.EntireRow.Hidden = true;
            nCurrentRow++;
        }

        /// <summary>
        /// Inserts the pie chart of entity sentiment across mentions
        /// </summary>
        /// <param name="aEntity">The entity</param>
        /// <param name="nBeginRow">Beginning row of entity details section</param>
        /// <param name="nEndRow">End row of entity details section</param>
        private void InsertEntitySentimentChart(LXAEntity aEntity, int nBeginRow, int nEndRow)
        {
            // Set header and labels
            Excel.Range r = (Excel.Range)_wsEntity.Cells[nBeginRow, "E"];
            r.Value2 = "Sentiment chart";
            r = (Excel.Range)_wsEntity.Cells[nBeginRow + 1, "E"];
            r.Value2 = "Neutral";
            r = (Excel.Range)_wsEntity.Cells[nBeginRow + 2, "E"];
            r.Value2 = "Negative";
            r = (Excel.Range)_wsEntity.Cells[nBeginRow + 3, "E"];
            r.Value2 = "Positive";

            // Tally up percentages
            int nPos = 0;
            int nNeg = 0;
            int nNeu = 0;
            int nTot = 0;

            foreach (LXAEntityMention aMention in aEntity.MentionList)
            {
                // CJL 2009-08-27
                // Adjust use of sentiment
                nTot++;
                if (aMention.Sentiment > 0)
                    nPos++;
                else if (aMention.Sentiment < 0)
                    nNeg++;
                else
                    nNeu++;
            }

            if (nPos + nNeg + nNeu > 0)
            {
                r = (Excel.Range)_wsEntity.Cells[nBeginRow + 1, "F"];
                r.Value2 = Convert.ToDouble(nNeu) / Convert.ToDouble(nTot);
                r = (Excel.Range)_wsEntity.Cells[nBeginRow + 2, "F"];
                r.Value2 = Convert.ToDouble(nNeg) / Convert.ToDouble(nTot);
                r = (Excel.Range)_wsEntity.Cells[nBeginRow + 3, "F"];
                r.Value2 = Convert.ToDouble(nPos) / Convert.ToDouble(nTot);

                // Insert basic pie chart object
                Excel.ChartObjects charts = (Excel.ChartObjects)_wsEntity.ChartObjects(Type.Missing);

                // Gets the cells that define the bounds of the data to be charted
                Excel.Range chartTopLeft = (Excel.Range)_wsEntity.Cells[nBeginRow, "E"];
                double chartX = Convert.ToDouble(chartTopLeft.Left);
                double chartY = Convert.ToDouble(chartTopLeft.Top);
                Excel.Range chartBottomRight = (Excel.Range)_wsEntity.Cells[nBeginRow + 5, "J"];
                double chartX2 = Convert.ToDouble(chartBottomRight.Left);
                double chartY2 = Convert.ToDouble(chartBottomRight.Top);

                Excel.ChartObject chartObj = charts.Add(chartX, chartY, chartX2 - chartX, chartY2 - chartY);
                Excel.Chart chart = chartObj.Chart;
                chart.ChartType = Excel.XlChartType.xlPie;

                // Select the data series
                Excel.SeriesCollection seriesCollection = (Excel.SeriesCollection)chart.SeriesCollection(Type.Missing);
                Excel.Range chartRange = _wsEntity.get_Range(String.Format("={0}!$E${1}:$F${2}", _wsEntity.Name, nBeginRow + 1, nBeginRow + 3), Type.Missing);
                chart.SetSourceData(chartRange, Type.Missing);

                // Format legend, data labels
                chart.HasLegend = false;
                chart.ApplyDataLabels(Microsoft.Office.Interop.Excel.XlDataLabelsType.xlDataLabelsShowLabelAndPercent, Type.Missing, Type.Missing, true, false, true, false, true, false, Type.Missing);
            }
        }
    }
}
